/*
 * Copyright (C) 2001-2011 Jacek Sieka, arnetheduck on gmail point com
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "stdinc.h"
#include "Util.h"
#include "version.h"

#ifdef _WIN32

#include "w.h"
#include "shlobj.h"

#endif

#include "CID.h"
#include "FastAlloc.h"
#include "File.h"
#include "SettingsManager.h"
#include "ResourceManager.h"
#include "StringTokenizer.h"
#include "SettingsManager.h"
#include "UploadManager.h" // [+]IRainman
#include "SimpleXML.h"
#include "version.h"
#include "OnlineUser.h"

#include "User.h"
#include <fstream>
#include "wininet.h"

#ifndef _WIN32
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/utsname.h>
#include <ctype.h>
#endif
#include <locale.h>

#include <boost/algorithm/string.hpp>
#include "../client/LogManager.h"
#include "../windows/resource.h" // TODO - ieioi ?oi ooo eieeoaeony aeiaa? Aa a?iaa ia no?aoii, yoi ?a nienie ?ano?nia, iaaienu oea?ee ia?nyony aey userlocation :)

#include "../client/idna/idna.h" // [+] SSA
#include "../client/MD5Calc.h" // [+] SSA

#ifdef PPA_USE_FAST_ALLOC
FastCriticalSection FastAllocBase::cs;
#endif
CriticalSection Util::g_cs;
time_t Util::startTime = time(NULL);
string Util::emptyString;
wstring Util::emptyStringW;
tstring Util::emptyStringT;

string Util::m_dot = ".";
string Util::m_dot_dot = "..";
tstring Util::m_dotT = _T(".");
tstring Util::m_dot_dotT = _T("..");

bool Util::away = false;
string Util::awayMsg;
time_t Util::awayTime;

OSVERSIONINFOEX Util::m_osvi = {0};
bool Util::m_isXPSP3andHigher = false;

// [+] IRainman
// This value for the majority of currently existing processors
// is less than the minimum size of the cache level 1 (8096 B ~ P3 level).
// However, this value is more than the usual minimum of the memory size page (4096 B).
const static size_t g_DefaultHashManagerBufferSize = 8096 * 1024;

size_t Util::m_HashManagerBufferSize = 0;
// [~] IRainman

Util::CountryList Util::countries;
Util::LocationsList Util::userLocations;
unordered_set<string> Util::g_compress_ext;

StringList Util::countryNames;

string Util::paths[Util::PATH_LAST];

bool Util::localMode = true;

static void sgenrand(unsigned long seed);

extern "C" void bz_internal_error(int errcode)
{
	dcdebug("bzip2 internal error: %d\n", errcode);
}

#if (_MSC_VER >= 1400 )
void WINAPI invalidParameterHandler(const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, uintptr_t)
{
	//do nothing, this exist because vs2k5 crt needs it not to crash on errors.
}
#endif
void Util::initialize()
{
	// [+] IRainman
	m_osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

	if (!GetVersionEx((OSVERSIONINFO*)&m_osvi))
		memzero(&m_osvi, sizeof(OSVERSIONINFOEX));

	if (m_osvi.dwMajorVersion >= 6 || // Vista and later
		(m_osvi.dwMajorVersion == 5 && m_osvi.dwMinorVersion >= 2) || // Win2k3 and later
		(m_osvi.dwMajorVersion == 5 && m_osvi.dwMinorVersion == 1 && m_osvi.wServicePackMajor >= 3)) // XP with SP3 
		m_isXPSP3andHigher = true;

	// [~] IRainman

	Text::initialize();
	
	sgenrand((unsigned long)time(NULL));
	
#if (_MSC_VER >= 1400)
	_set_invalid_parameter_handler(reinterpret_cast<_invalid_parameter_handler>(invalidParameterHandler));
#endif
	
#ifdef _WIN32
	TCHAR buf[MAX_PATH] = { 0 };
	::GetModuleFileName(NULL, buf, MAX_PATH);
	
	string exePath = Util::getFilePath(Text::fromT(buf));
	
	// Global config path is FlylinkDC++ executable path...
	paths[PATH_EXE] = exePath;
	paths[PATH_GLOBAL_CONFIG] = exePath;
#ifdef USE_APPDATA //[+] NightOrion
	SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL, 0, buf);
	if ((Util::fileExists(exePath + "Settings\\DCPlusPlus.xml")) || (::strstr(exePath.c_str(), Text::wideToUtf8(buf).c_str()) == NULL))
	{
		paths[PATH_USER_CONFIG] = paths[PATH_GLOBAL_CONFIG] + "Settings\\";
		paths[PATH_ALL_USER_CONFIG] = paths[PATH_GLOBAL_CONFIG] + "Settings\\";
	}
	else
	{
		SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, buf);
		paths[PATH_USER_CONFIG] = Text::wideToUtf8(buf) + "\\FlylinkDC++\\";
		SHGetFolderPath(NULL, CSIDL_COMMON_APPDATA, NULL, 0, buf);
		paths[PATH_ALL_USER_CONFIG] = Text::wideToUtf8(buf) + "\\FlylinkDC++\\";
	}
#else
	paths[PATH_USER_CONFIG] = paths[PATH_GLOBAL_CONFIG] + "Settings\\";
#endif //USE_APPDATA    
	paths[PATH_LANGUAGES] = paths[PATH_GLOBAL_CONFIG] + "Lang\\";
	
	paths[PATH_EXTERNAL_ICO] = paths[PATH_GLOBAL_CONFIG] + "FlylinkDC.ico";//[+] IRainman
	
	paths[PATH_THEMES] = paths[PATH_GLOBAL_CONFIG] + "Themes\\";

	paths[PATH_SOUNDS] = paths[PATH_GLOBAL_CONFIG] + "Sounds\\";
	
	loadBootConfig();
	
	if (!File::isAbsolute(paths[PATH_USER_CONFIG]))
	{
		paths[PATH_USER_CONFIG] = paths[PATH_GLOBAL_CONFIG] + paths[PATH_USER_CONFIG];
	}
	
	paths[PATH_USER_CONFIG] = validateFileName(paths[PATH_USER_CONFIG]);
	
	if (localMode)
	{
		paths[PATH_USER_LOCAL] = paths[PATH_USER_CONFIG];
	}
	else
	{
		if (::SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, buf) == S_OK)
		{
			paths[PATH_USER_CONFIG] = Text::fromT(buf) + "\\FlylinkDC++\\";
		}
		
		paths[PATH_USER_LOCAL] = ::SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, buf) == S_OK ? Text::fromT(buf) + "\\FlylinkDC++\\" : paths[PATH_USER_CONFIG];
	}

	const char* downloads = g_isWineCheck ? getenv("HOME") : getenv("SystemDrive");
	const string downloads_s = downloads ? downloads : (g_isWineCheck ? "\\tmp" : "C:\\");
	paths[PATH_DOWNLOADS] = getDownloadPath(downloads_s + "\\");
//	paths[PATH_RESOURCES] = exePath;
	paths[PATH_WEB_SERVER] = exePath + "WEBserver\\";
	
	
	if (!g_isWineCheck && IsXPSP3AndHigher())
	{
// [+] IRainman increase hashing speed
		// [+] SSA dynamic call for SP3 function
		typedef  BOOL (WINAPI _tGetLogicalProcessorInformation)(PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, PDWORD);
		HINSTANCE hModule =::LoadLibrary(L"Kernel32.dll");
		if (hModule != NULL)
		{
			//BOOL
			//WINAPI
			//GetLogicalProcessorInformation(
			//__out_bcount_part_opt(*ReturnedLength, *ReturnedLength) PSYSTEM_LOGICAL_PROCESSOR_INFORMATION Buffer,
			//__inout PDWORD ReturnedLength
			//);
			_tGetLogicalProcessorInformation* _GetLogicalProcessorInformation = (_tGetLogicalProcessorInformation*)::GetProcAddress((HMODULE)hModule, "GetLogicalProcessorInformation");
			
			DWORD l_Bufsize = 0;
			if (_GetLogicalProcessorInformation != NULL && _GetLogicalProcessorInformation(0, &l_Bufsize) == FALSE && GetLastError() == ERROR_INSUFFICIENT_BUFFER)
			{
				const DWORD l_Count = l_Bufsize / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION);
				boost::scoped_array<SYSTEM_LOGICAL_PROCESSOR_INFORMATION> l_buf(new SYSTEM_LOGICAL_PROCESSOR_INFORMATION[l_Count]);
				if (_GetLogicalProcessorInformation(&l_buf[0], &l_Bufsize) == TRUE)
				{
					for (DWORD i = 0; i != l_Count; ++i)
					{
						if (l_buf[i].Relationship == RelationCache && l_buf[i].Cache.Level == 1)
						{
							m_HashManagerBufferSize = l_buf[i].Cache.Size;
							break;
						}
					}
				}
			}
			::FreeLibrary(hModule);
		}
// [~] IRainman increase hashing speed
	}
#else
	paths[PATH_GLOBAL_CONFIG] = "/etc/";
	const char* home_ = getenv("HOME");
	string home = home_ ? Text::toUtf8(home_) : "/tmp/";
	
	paths[PATH_USER_CONFIG] = home + "/.flylinkdc++/";
	
	loadBootConfig();
	
	if (!File::isAbsolute(paths[PATH_USER_CONFIG]))
	{
		paths[PATH_USER_CONFIG] = paths[PATH_GLOBAL_CONFIG] + paths[PATH_USER_CONFIG];
	}
	
	paths[PATH_USER_CONFIG] = validateFileName(paths[PATH_USER_CONFIG]);
	
	if (localMode)
	{
		// @todo implement...
	}
	
	paths[PATH_USER_LOCAL] = paths[PATH_USER_CONFIG];
	paths[PATH_RESOURCES] = "/usr/share/";
	paths[PATH_LOCALE] = paths[PATH_RESOURCES] + "locale/";
	paths[PATH_DOWNLOADS] = home + "/Downloads/";
	
// [+] IRainman increase hashing speed
	FILE* l_LevelOneCacheSize = 0;
	l_LevelOneCacheSize = fopen("/sys/devices/system/cpu/cpu0/cache/index0/size", "r");
	if (l_LevelOneCacheSize)
	{
		fscanf(l_LevelOneCacheSize, "%d", &m_HashManagerBufferSize);
		fclose(l_LevelOneCacheSize);
	}
// [~] IRainman increase hashing speed
#endif
	
	// [+] IRainman increase hashing speed
	if (!m_HashManagerBufferSize)
		m_HashManagerBufferSize = g_DefaultHashManagerBufferSize;
	// [~] IRainman increase hashing speed
	
	paths[PATH_FILE_LISTS] = paths[PATH_USER_LOCAL] + "FileLists" PATH_SEPARATOR_STR;
	paths[PATH_HUB_LISTS] = paths[PATH_USER_LOCAL] + "HubLists" PATH_SEPARATOR_STR;
	paths[PATH_NOTEPAD] = paths[PATH_USER_CONFIG] + "Notepad.txt";
	paths[PATH_EMOPACKS] = paths[PATH_GLOBAL_CONFIG] + "EmoPacks" PATH_SEPARATOR_STR;
	
	File::ensureDirectory(paths[PATH_USER_CONFIG]);
	File::ensureDirectory(paths[PATH_USER_LOCAL]);
	
	try
	{
		// This product includes GeoIP data created by MaxMind, available from http://maxmind.com/
		// Updates at http://www.maxmind.com/app/geoip_country
		const string file = getConfigPath(true) + "GeoIpCountryWhois.csv";
		const string data = File(file, File::READ, File::OPEN).read();
		
		const char* start = data.c_str();
		string::size_type linestart = 0;
		string::size_type lineend = 0;
		auto last = countries.end();
		uint32_t startIP = 0;
		uint32_t endIP = 0, endIPprev = 0;
		
		countryNames.push_back("Unknown"); //[-]PPA STRING(UNKNOWN) ResourceManager aua ia nicaai...
		auto addCountry = [](const string& countryName) -> size_t
		{
			auto begin = countryNames.cbegin(), end = countryNames.cend();
			auto pos = std::find(begin, end, countryName);
			if(pos != end)
				return pos - begin;
			countryNames.push_back(countryName);
			return countryNames.size() - 1;
		};

		while(true)
		{
			auto pos = data.find(',', linestart);
			if(pos == string::npos) break;
			pos = data.find(',', pos + 1);
			if(pos == string::npos) break;
			startIP = toUInt32(start + pos + 2) - 1;

			pos = data.find(',', pos + 1);
			if(pos == string::npos) break;
			endIP = toUInt32(start + pos + 2);

			pos = data.find(',', pos + 1);
			if(pos == string::npos) break;
			pos = data.find(',', pos + 1);
			if(pos == string::npos) break;
			lineend = data.find('\n', pos);
			if (lineend == string::npos) break;
			
			if(startIP != endIPprev)
				last = countries.insert(last, make_pair(startIP, 0));
			pos += 2;
			last = countries.insert(last, make_pair(endIP, addCountry(data.substr(pos, lineend - 1 - pos))));
			
			endIPprev = endIP;
			linestart = lineend + 1;
		}
	}
	catch (const FileException&)
	{
	}
#ifdef USE_APPDATA //[+] NightOrion
	File::ensureDirectory(getConfigPath(true));
#endif
	File::ensureDirectory(getConfigPath());
}

bool Util::is_compress_ext(const string& p_ext)
{
	Lock l(g_cs);
	return g_compress_ext.count(p_ext) > 0;
}

void Util::load_compress_ext()
{
	Lock l(g_cs);
	const tstring l_filename = Text::toT(getConfigPath(true)) + _T("CustomCompressExt.ini");
	std::ifstream l_file_in(l_filename.c_str());
	string l_CurLine;
	bool l_end_file;
	if (l_file_in)
	{
		do
		{
			l_end_file = getline(l_file_in, l_CurLine).eof();
			if (!l_CurLine.empty() && l_CurLine[0] == '.')
			{
				string l_ext;
				for (size_t i = 0; i < l_CurLine.size(); ++i)
				{
					if (l_CurLine[i] == ' ' || l_CurLine[i] == 0x09 || l_CurLine[i] == 0x0D || l_CurLine[i] == 0x0A)
						break;
					l_ext += l_CurLine[i];
				}
				if (!l_ext.empty())
				{
					std::pair< unordered_set<string>::const_iterator, bool > l_it = g_compress_ext.insert(Text::toLower(l_ext));
					if (!l_it.second)
						LogManager::getInstance()->message("CustomCompressExt.ini - " + STRING(DUPLICATE_EXTENSIONS) + ": " + l_ext);
				}
			}
		}
		while (!l_end_file);
	}
	else
	{
		if (HRSRC hResInfo = FindResource(NULL, MAKEINTRESOURCE(IDR_CUSTOM_COMPRESS_EXT), RT_RCDATA))
			if (HGLOBAL hResGlobal = LoadResource(NULL, hResInfo))
				if (LPCSTR l_data = (LPCSTR)LockResource(hResGlobal))
					if (const int l_size = SizeofResource(NULL, hResInfo))
					{
						std::ofstream l_file_out(l_filename.c_str());
						l_file_out.write(l_data, l_size);
					}
	}
}

void Util::load_customlocations()
{
	Lock l(g_cs);
	// !SMT!-IP loading custom networks
	const tstring l_filename = Text::toT(getConfigPath(true)) + _T("CustomLocations.ini");
	std::ifstream l_file_in(l_filename.c_str());
	string l_CurLine;
	bool l_end_file;
	if (l_file_in)
		do
		{
			const char* l_LogLine = "CustomLocations.ini: [";
			l_end_file = getline(l_file_in, l_CurLine).eof();
			if (!l_CurLine.empty() && isdigit((unsigned char)l_CurLine[0]))
				if (l_CurLine.find('-') != string::npos && count(l_CurLine.begin(), l_CurLine.end(), '.') >= 6)
				{
					unsigned a = 0, b = 0, c = 0, d = 0, a2 = 0, b2 = 0, c2 = 0, d2 = 0;
					const int l_Items = sscanf_s(l_CurLine.c_str(), "%d.%d.%d.%d-%d.%d.%d.%d", &a, &b, &c, &d, &a2, &b2, &c2, &d2);
					if (l_Items == 8)
					{
						const string::size_type l_space = l_CurLine.find(' ');
						if (l_space != string::npos)
						{
							CustomNetwork net;
							net.startip = (a << 24) + (b << 16) + (c << 8) + d;
							net.endip = (a2 << 24) + (b2 << 16) + (c2 << 8) + d2 + 1;
							net.m_description = l_CurLine.substr(l_space + 1);
							boost::trim_left(net.m_description);
							userLocations.push_back(net);
						}
						else
							LogManager::getInstance()->message(l_LogLine + l_CurLine + "] " + STRING(SPACE_NOT_FOUND));
					}
					else
						LogManager::getInstance()->message(l_LogLine + l_CurLine + "] " + STRING(MASK_NOT_FOUND) + " %d.%d.%d.%d-%d.%d.%d.%d");
				}
				else if (l_CurLine.find('+') != string::npos && count(l_CurLine.begin(), l_CurLine.end(), '.') >= 3)
				{
					unsigned a = 0, b = 0, c = 0, d = 0, n = 0;
					const int l_Items = sscanf_s(l_CurLine.c_str(), "%d.%d.%d.%d+%d", &a, &b, &c, &d, &n);
					if (l_Items == 5)
					{
						const string::size_type l_space = l_CurLine.find(' ');
						if (l_space != string::npos)
						{
							CustomNetwork net;
							net.startip = (a << 24) + (b << 16) + (c << 8) + d;
							net.endip = net.startip + n;
							net.m_description = l_CurLine.substr(l_space + 1);
							boost::trim_left(net.m_description);
							userLocations.push_back(net);
						}
						else
							LogManager::getInstance()->message(l_LogLine + l_CurLine + "] " + STRING(SPACE_NOT_FOUND));
					}
					else
						LogManager::getInstance()->message(l_LogLine + l_CurLine + "] " + STRING(MASK_NOT_FOUND) + " %d.%d.%d.%d+%d");
				}
		}
		while (!l_end_file);
		
}

void Util::migrate(const string& file)
{
	if (localMode)
	{
		return;
	}
	
	if (File::getSize(file) != -1)
	{
		return;
	}
	
	string fname = getFileName(file);
	string old = paths[PATH_GLOBAL_CONFIG] + "Settings\\" + fname;
	if (File::getSize(old) == -1)
	{
		return;
	}
	
	File::renameFile(old, file);
}

void Util::loadBootConfig()
{
	// Load boot settings
	try
	{
		SimpleXML boot;
		boot.fromXML(File(getPath(PATH_GLOBAL_CONFIG) + "dcppboot.xml", File::READ, File::OPEN).read());
		boot.stepIn();
		
		if (boot.findChild("LocalMode"))
		{
			localMode = boot.getChildData() != "0";
		}
		
		boot.resetCurrentChild();
		
		if (boot.findChild("ConfigPath"))
		{
			StringMap params;
#ifdef _WIN32
			// @todo load environment variables instead? would make it more useful on *nix
			TCHAR path[MAX_PATH];
			
			params["APPDATA"] = Text::fromT((::SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, path), path));
			params["PERSONAL"] = Text::fromT((::SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, path), path));
#endif
			
#ifdef USE_APPDATA //[+] NightOrion
			paths[PATH_ALL_USER_CONFIG] = formatParams(boot.getChildData(), params, false);
#endif
			paths[PATH_USER_CONFIG] = formatParams(boot.getChildData(), params, false);
		}
	}
	catch (const Exception&)
	{
		// Unable to load boot settings...
	}
}

#ifdef _WIN32
static const char badChars[] =
{
	1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
	17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
	31, '<', '>', '/', '"', '|', '?', '*', 0
};
#else

static const char badChars[] =
{
	1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
	17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
	31, '<', '>', '\\', '"', '|', '?', '*', 0
};
#endif

/**
 * Replaces all strange characters in a file with '_'
 * @todo Check for invalid names such as nul and aux...
 */
string Util::validateFileName(string tmp)
{
	string::size_type i = 0;
	
	// First, eliminate forbidden chars
	while ((i = tmp.find_first_of(badChars, i)) != string::npos)
	{
		tmp[i] = '_';
		i++;
	}
	
	// Then, eliminate all ':' that are not the second letter ("c:\...")
	i = 0;
	while ((i = tmp.find(':', i)) != string::npos)
	{
		if (i == 1)
		{
			i++;
			continue;
		}
		tmp[i] = '_';
		i++;
	}
	
	// Remove the .\ that doesn't serve any purpose
	i = 0;
	while ((i = tmp.find("\\.\\", i)) != string::npos)
	{
		tmp.erase(i + 1, 2);
	}
	i = 0;
	while ((i = tmp.find("/./", i)) != string::npos)
	{
		tmp.erase(i + 1, 2);
	}
	
	// Remove any double \\ that are not at the beginning of the path...
	i = 1;
	while ((i = tmp.find("\\\\", i)) != string::npos)
	{
		tmp.erase(i + 1, 1);
	}
	i = 1;
	while ((i = tmp.find("//", i)) != string::npos)
	{
		tmp.erase(i + 1, 1);
	}
	
	// And last, but not least, the infamous ..\! ...
	i = 0;
	while (((i = tmp.find("\\..\\", i)) != string::npos))
	{
		tmp[i + 1] = '_';
		tmp[i + 2] = '_';
		tmp[i + 3] = '_';
		i += 2;
	}
	i = 0;
	while (((i = tmp.find("/../", i)) != string::npos))
	{
		tmp[i + 1] = '_';
		tmp[i + 2] = '_';
		tmp[i + 3] = '_';
		i += 2;
	}
	
	// Dots at the end of path names aren't popular
	i = 0;
	while (((i = tmp.find(".\\", i)) != string::npos))
	{
		if (i != 0)
			tmp[i] = '_';
		i += 1;
	}
	i = 0;
	while (((i = tmp.find("./", i)) != string::npos))
	{
		if (i != 0)
			tmp[i] = '_';
		i += 1;
	}
	
	
	return tmp;
}

string Util::cleanPathChars(string aNick)
{
	string::size_type i = 0;
	
	while ((i = aNick.find_first_of("/.\\", i)) != string::npos)
	{
		aNick[i] = '_';
	}
	return aNick;
}

string Util::getShortTimeString(time_t t)
{
	char buf[255];
	tm* _tm = localtime(&t);
	if (_tm == NULL)
	{
		strcpy(buf, "xx:xx");
	}
	else
	{
		strftime(buf, 254, SETTING(TIME_STAMPS_FORMAT).c_str(), _tm);
	}
	return Text::toUtf8(buf);
}

/**
 * Decodes a URL the best it can...
 * Default ports:
 * http:// -> port 80
 * https:// -> port 443
 * dchub:// -> port 411
 */
void Util::decodeUrl(const string& url, string& protocol, string& host, uint16_t& port, string& path, bool& isSecure, string& query, string& fragment)
{
	auto fragmentEnd = url.size();
	auto fragmentStart = url.rfind('#');
	
	size_t queryEnd;
	if(fragmentStart == string::npos)
	{
		queryEnd = fragmentStart = fragmentEnd;
	} else {
		dcdebug("f");
		queryEnd = fragmentStart;
		fragmentStart++;
	}

	auto queryStart = url.rfind('?', queryEnd);
	size_t fileEnd;

	if(queryStart == string::npos)
	{
		fileEnd = queryStart = queryEnd;
	}
	else
	{
		dcdebug("q");
		fileEnd = queryStart;
		queryStart++;
	}

	auto protoStart = 0;
	auto protoEnd = url.find("://", protoStart);

	auto authorityStart = protoEnd == string::npos ? protoStart : protoEnd + 3;
	auto authorityEnd = url.find_first_of("/#?", authorityStart);

	size_t fileStart;
	if(authorityEnd == string::npos)
	{
		authorityEnd = fileStart = fileEnd;
	} else {
		dcdebug("a");
		fileStart = authorityEnd;
	}

	protocol = (protoEnd == string::npos ? Util::emptyString : url.substr(protoStart, protoEnd - protoStart));

	if(authorityEnd > authorityStart)
	{
		dcdebug("x");
		size_t portStart = string::npos;
		if(url[authorityStart] == '[')
		{
			// IPv6?
			auto hostEnd = url.find(']');
			if(hostEnd == string::npos)
			{
				return;
			}

			host = url.substr(authorityStart + 1, hostEnd - authorityStart - 1);
			if(hostEnd + 1 < url.size() && url[hostEnd + 1] == ':')
			{
				portStart = hostEnd + 2;
			}
		}
		else
		{
			size_t hostEnd;
			portStart = url.find(':', authorityStart);
			if(portStart != string::npos && portStart > authorityEnd)
			{
				portStart = string::npos;
			}

			if(portStart == string::npos)
			{
				hostEnd = authorityEnd;
			}
			else
			{
				hostEnd = portStart;
				portStart++;
			}

			dcdebug("h");
			host = url.substr(authorityStart, hostEnd - authorityStart);
		}

		if(portStart == string::npos)
		{
			if(protocol == "http" || protocol == "steam")
			{
				port = 80;
			}
			else if(protocol == "https")
			{
				port = 443;
				isSecure = true;
			}
			else if(protocol == "dchub"  || protocol.empty())
			{
				port = 411;
			}
			else if (protocol == "mtasa")
			{
				port = 22004;
			}
			else if (protocol == "samp")
			{
				port = 7790;
			}
		}
		else
		{
			dcdebug("p");
			port = static_cast<uint16_t>(Util::toInt(url.substr(portStart, authorityEnd - portStart)));
		}
	}

	dcdebug("\n");
	path = url.substr(fileStart, fileEnd - fileStart);
	query = url.substr(queryStart, queryEnd - queryStart);
	fragment = url.substr(fragmentStart, fragmentEnd - fragmentStart);  //http://bazaar.launchpad.net/~dcplusplus-team/dcplusplus/trunk/revision/2606

	bool l_is_IDNA = true;
	for(size_t i = 0; i < host.size(); ++i )
		l_is_IDNA &= __isascii(host[i]);

	if(!l_is_IDNA) //[+]PPA  !Text::isAscii(host) - aaoaaony...
	{
	// [+] SSA make make idna conversion
	//string hostSaveString = host;
	//try
	//{
		BOOL success = IDNA_init(0);
		if (success)
		{
			size_t size = MAX_HOST_LEN;
			string hostUTF8 = Text::utf8ToAcp(host);
			boost::scoped_array<char>buff(new char[size]);
			strcpy(buff.get(), host.c_str());
			if (IDNA_convert_to_ACE(buff.get(), &size))
			{
				hostUTF8 = buff.get();
				host = Text::acpToUtf8(hostUTF8);
			}
		}
	//}
	//catch (...)
	//{
	//	host = hostSaveString;
	//}
	// [+] SSA
	}
}

map<string, string> Util::decodeQuery(const string& query)
{
	map<string, string> ret;
	size_t start = 0;
	while(start < query.size())
	{
		auto eq = query.find('=', start);
		if(eq == string::npos)
		{
			break;
		}

		auto param = eq + 1;
		auto end = query.find('&', param);

		if(end == string::npos)
		{
			end = query.size();
		}

		if(eq > start && end > param)
		{
			ret[query.substr(start, eq-start)] = query.substr(param, end - param);
		}

		start = end + 1;
	}

	return ret;
}


void Util::setAway(bool aAway)
{
	away = aAway;
	
	SET_SETTING(AWAY, aAway);
	
	if (away)
		awayTime = time(NULL);
}
// [~] InfinitySky. ?aaioa n aaoiioaao?eeii.
string Util::getAwayMessage(StringMap& params)
{
	time_t currentTime;
	time(&currentTime);
	int currentHour = localtime(&currentTime)->tm_hour;
	
	params["idleTI"] = Text::fromT(formatSeconds(time(NULL) - awayTime));
	
	if (BOOLSETTING(AWAY_TIME_THROTTLE) && ((SETTING(AWAY_START) < SETTING(AWAY_END) &&
	                                         currentHour >= SETTING(AWAY_START) && currentHour < SETTING(AWAY_END)) ||
	                                        (SETTING(AWAY_START) > SETTING(AWAY_END) &&
	                                         (currentHour >= SETTING(AWAY_START) || currentHour < SETTING(AWAY_END)))))
	{
		return formatParams((awayMsg.empty() ? SETTING(SECONDARY_AWAY_MESSAGE) : awayMsg), params, false, awayTime);
	}
	else
	{
		return formatParams((awayMsg.empty() ? SETTING(DEFAULT_AWAY_MESSAGE) : awayMsg), params, false, awayTime);
	}
}

string Util::formatBytes(int64_t aBytes)
{
	char buf[64];
	if (aBytes < 1024)
	{
		snprintf(buf, _countof(buf), "%d %s", (int)(aBytes & 0xffffffff), CSTRING(B));
	}
	else if (aBytes < 1048576)
	{
		snprintf(buf, _countof(buf), "%.02f %s", (double)aBytes / (1024.0), CSTRING(KB));
	}
	else if (aBytes < 1073741824)
	{
		snprintf(buf, _countof(buf), "%.02f %s", (double)aBytes / (1048576.0), CSTRING(MB));
	}
	else if (aBytes < (int64_t)1099511627776)
	{
		snprintf(buf, _countof(buf), "%.02f %s", (double)aBytes / (1073741824.0), CSTRING(GB));
	}
	else if (aBytes < (int64_t)1125899906842624)
	{
		snprintf(buf, _countof(buf), "%.03f %s", (double)aBytes / (1099511627776.0), CSTRING(TB));
	}
	else if (aBytes < (int64_t)1152921504606846976)
	{
		snprintf(buf, _countof(buf), "%.03f %s", (double)aBytes / (1125899906842624.0), CSTRING(PB));
	}
	else
	{
		snprintf(buf, _countof(buf), "%.03f %s", (double)aBytes / (1152921504606846976.0), CSTRING(EB));
	}
	
	return buf;
}

wstring Util::formatBytesW(int64_t aBytes)
{
	wchar_t buf[64];
	if (aBytes < 1024)
	{
		snwprintf(buf, _countof(buf), L"%d %s", (int)(aBytes & 0xffffffff), CWSTRING(B));
	}
	else if (aBytes < 1048576)
	{
		snwprintf(buf, _countof(buf), L"%.02f %s", (double)aBytes / (1024.0), CWSTRING(KB));
	}
	else if (aBytes < 1073741824)
	{
		snwprintf(buf, _countof(buf), L"%.02f %s", (double)aBytes / (1048576.0), CWSTRING(MB));
	}
	else if (aBytes < (int64_t)1099511627776)
	{
		snwprintf(buf, _countof(buf), L"%.02f %s", (double)aBytes / (1073741824.0), CWSTRING(GB));
	}
	else if (aBytes < (int64_t)1125899906842624)
	{
		snwprintf(buf, _countof(buf), L"%.03f %s", (double)aBytes / (1099511627776.0), CWSTRING(TB));
	}
	else if (aBytes < (int64_t)1152921504606846976)
	{
		snwprintf(buf, _countof(buf), L"%.03f %s", (double)aBytes / (1125899906842624.0), CWSTRING(PB));
	}
	else
	{
		snwprintf(buf, _countof(buf), L"%.03f %s", (double)aBytes / (1152921504606846976.0), CWSTRING(EB));
	}
	
	return buf;
}

wstring Util::formatExactSize(int64_t aBytes)
{
#ifdef _WIN32
	wchar_t buf[64];
	wchar_t number[64];
	NUMBERFMT nf;
	snwprintf(number, sizeof(number), _T("%I64d"), aBytes);
	wchar_t Dummy[16];
	TCHAR sep[2] = _T(",");
	
	/*No need to read these values from the system because they are not
	used to format the exact size*/
	nf.NumDigits = 0;
	nf.LeadingZero = 0;
	nf.NegativeOrder = 0;
	nf.lpDecimalSep = sep;
	
	GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SGROUPING, Dummy, 16);
	nf.Grouping = _wtoi(Dummy);
	GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, Dummy, 16);
	nf.lpThousandSep = Dummy;
	
	GetNumberFormat(LOCALE_USER_DEFAULT, 0, number, &nf, buf, 64);
	
	snwprintf(buf, _countof(buf), _T("%s %s"), buf, CTSTRING(B));
	return buf;
#else
	wchar_t buf[64];
	snwprintf(buf, _countof(buf), L"%'lld", (long long int)aBytes);
	return tstring(buf) + TSTRING(B);
#endif
}

string Util::getLocalIp()
{
	string tmp;
	
	char buf[256];
	gethostname(buf, 255);
	hostent* he = gethostbyname(buf);
	if (he == NULL || he->h_addr_list[0] == 0)
		return Util::emptyString;
	sockaddr_in dest;
	int i = 0;
	
	// We take the first ip as default, but if we can find a better one, use it instead...
	memcpy(&(dest.sin_addr), he->h_addr_list[i++], he->h_length);
	tmp = inet_ntoa(dest.sin_addr);
	if (Util::isPrivateIp(tmp) || strncmp(tmp.c_str(), "169", 3) == 0)
	{
		while (he->h_addr_list[i])
		{
			memcpy(&(dest.sin_addr), he->h_addr_list[i], he->h_length);
			string tmp2 = inet_ntoa(dest.sin_addr);
			if (!Util::isPrivateIp(tmp2) && strncmp(tmp2.c_str(), "169", 3) != 0)
			{
				tmp = tmp2;
			}
			i++;
		}
	}
	return tmp;
}

bool Util::isPrivateIp(string const& ip)
{
	struct in_addr addr;
	
	addr.s_addr = inet_addr(ip.c_str());
	
	if (addr.s_addr  != INADDR_NONE)
	{
		unsigned long haddr = ntohl(addr.s_addr);
		return ((haddr & 0xff000000) == 0x0a000000 || // 10.0.0.0/8
		        (haddr & 0xff000000) == 0x7f000000 || // 127.0.0.0/8
		        (haddr & 0xffff0000) == 0xa9fe0000 || // 169.254.0.0/16
		        (haddr & 0xfff00000) == 0xac100000 || // 172.16.0.0/12
		        (haddr & 0xffff0000) == 0xc0a80000);  // 192.168.0.0/16
	}
	return false;
}

typedef const uint8_t* ccp;
static wchar_t utf8ToLC(ccp& str)
{
	wchar_t c = 0;
	if (str[0] & 0x80)
	{
		if (str[0] & 0x40)
		{
			if (str[0] & 0x20)
			{
				if (str[1] == 0 || str[2] == 0 ||
				        !((((unsigned char)str[1]) & ~0x3f) == 0x80) ||
				        !((((unsigned char)str[2]) & ~0x3f) == 0x80))
				{
					str++;
					return 0;
				}
				c = ((wchar_t)(unsigned char)str[0] & 0xf) << 12 |
				    ((wchar_t)(unsigned char)str[1] & 0x3f) << 6 |
				    ((wchar_t)(unsigned char)str[2] & 0x3f);
				str += 3;
			}
			else
			{
				if (str[1] == 0 ||
				        !((((unsigned char)str[1]) & ~0x3f) == 0x80))
				{
					str++;
					return 0;
				}
				c = ((wchar_t)(unsigned char)str[0] & 0x1f) << 6 |
				    ((wchar_t)(unsigned char)str[1] & 0x3f);
				str += 2;
			}
		}
		else
		{
			str++;
			return 0;
		}
	}
	else
	{
		c = Text::asciiToLower((char)str[0]);
		str++;
		return c;
	}
	
	return Text::toLower(c);
}

string Util::toString(const string& sep, const StringList& lst)
{
	string ret;
	for (StringList::const_iterator i = lst.begin(), iend = lst.end(); i != iend; ++i)
	{
		ret += *i;
		if (i + 1 != iend)
			ret += sep;
	}
	return ret;
}

string Util::toString(const StringList& lst)
{
	if (lst.size() == 1)
		return lst[0];
	string tmp("[");
	for (StringList::const_iterator i = lst.begin(), iend = lst.end(); i != iend; ++i)
	{
		tmp += *i + ',';
	}
	if (tmp.length() == 1)
		tmp.push_back(']');
	else
		tmp[tmp.length() - 1] = ']';
	return tmp;
}

string::size_type Util::findSubString(const string& aString, const string& aSubString, string::size_type start) noexcept
{
	if (aString.length() < start)
		return (string::size_type)string::npos;
		
	if (aString.length() - start < aSubString.length())
		return (string::size_type)string::npos;
		
	if (aSubString.empty())
		return 0;
		
	// Hm, should start measure in characters or in bytes? bytes for now...
	const uint8_t* tx = (const uint8_t*)aString.c_str() + start;
	const uint8_t* px = (const uint8_t*)aSubString.c_str();
	
	const uint8_t* end = tx + aString.length() - start - aSubString.length() + 1;
	
	wchar_t wp = utf8ToLC(px);
	
	while (tx < end)
	{
		const uint8_t* otx = tx;
		if (wp == utf8ToLC(tx))
		{
			const uint8_t* px2 = px;
			const uint8_t* tx2 = tx;
			
			for (;;)
			{
				if (*px2 == 0)
					return otx - (uint8_t*)aString.c_str();
					
				if (utf8ToLC(px2) != utf8ToLC(tx2))
					break;
			}
		}
	}
	return (string::size_type)string::npos;
}

wstring::size_type Util::findSubString(const wstring& aString, const wstring& aSubString, wstring::size_type pos) noexcept
{
	if (aString.length() < pos)
		return static_cast<wstring::size_type>(wstring::npos);
		
	if (aString.length() - pos < aSubString.length())
		return static_cast<wstring::size_type>(wstring::npos);
		
	if (aSubString.empty())
		return 0;
		
	wstring::size_type j = 0;
	wstring::size_type end = aString.length() - aSubString.length() + 1;
	
	for (; pos < end; ++pos)
	{
		if (Text::toLower(aString[pos]) == Text::toLower(aSubString[j]))
		{
			wstring::size_type tmp = pos + 1;
			bool found = true;
			for (++j; j < aSubString.length(); ++j, ++tmp)
			{
				if (Text::toLower(aString[tmp]) != Text::toLower(aSubString[j]))
				{
					j = 0;
					found = false;
					break;
				}
			}
			
			if (found)
				return pos;
		}
	}
	return static_cast<wstring::size_type>(wstring::npos);
}

string Util::encodeURI(const string& aString, bool reverse)
{
	// reference: rfc2396
	string tmp = aString;
	if (reverse)
	{
		string::size_type idx;
		for (idx = 0; idx < tmp.length(); ++idx)
		{
			if (tmp.length() > idx + 2 && tmp[idx] == '%' && isxdigit(tmp[idx + 1]) && isxdigit(tmp[idx + 2]))
			{
				tmp[idx] = fromHexEscape(tmp.substr(idx + 1, 2));
				tmp.erase(idx + 1, 2);
			}
			else   // reference: rfc1630, magnet-uri draft
			{
				if (tmp[idx] == '+')
					tmp[idx] = ' ';
			}
		}
	}
	else
	{
		static const string disallowed = ";/?:@&=+$," // reserved
		                                 "<>#%\" "    // delimiters
		                                 "{}|\\^[]`"; // unwise
		string::size_type idx;
		for (idx = 0; idx < tmp.length(); ++idx)
		{
			if (tmp[idx] == ' ')
			{
				tmp[idx] = '+';
			}
			else
			{
				if (tmp[idx] <= 0x1F || tmp[idx] >= 0x7f || (disallowed.find_first_of(tmp[idx])) != string::npos)
				{
					tmp.replace(idx, 1, toHexEscape(tmp[idx]));
					idx += 2;
				}
			}
		}
	}
	return tmp;
}

/**
 * This function takes a string and a set of parameters and transforms them according to
 * a simple formatting rule, similar to strftime. In the message, every parameter should be
 * represented by %[name]. It will then be replaced by the corresponding item in
 * the params stringmap. After that, the string is passed through strftime with the current
 * date/time and then finally written to the log file. If the parameter is not present at all,
 * it is removed from the string completely...
 */
string Util::formatParams(const string& msg, const StringMap& params, bool filter, const time_t t)
{
	string result = msg;
	
	string::size_type i, j, k;
	i = 0;
	while ((j = result.find("%[", i)) != string::npos)
	{
		if ((result.size() < j + 2) || ((k = result.find(']', j + 2)) == string::npos))
		{
			break;
		}
		string name = result.substr(j + 2, k - j - 2);
		StringMapIterC smi = params.find(name);
		if (smi == params.end())
		{
			result.erase(j, k - j + 1);
			i = j;
		}
		else
		{
			if (smi->second.find_first_of("%\\./") != string::npos)
			{
				string tmp = smi->second;   // replace all % in params with %% for strftime
				string::size_type m = 0;
				while ((m = tmp.find('%', m)) != string::npos)
				{
					tmp.replace(m, 1, "%%");
					m += 2;
				}
				if (filter)
				{
					// Filter chars that produce bad effects on file systems
					m = 0;
#ifdef _WIN32 // !SMT!-f add windows special chars
					const char badchars[] = "\\./:*?|<>";
#else // unix is more tolerant
					const char badchars[] = "\\./";
#endif
					while ((m = tmp.find_first_of(badchars, m)) != string::npos)
					{
						tmp[m] = '_';
					}
				}
				
				result.replace(j, k - j + 1, tmp);
				i = j + tmp.size();
			}
			else
			{
				result.replace(j, k - j + 1, smi->second);
				i = j + smi->second.size();
			}
		}
	}
	
	result = formatTime(result, time(NULL));
	
	return result;
}

string Util::formatRegExp(const string& msg, const StringMap& params)
{
	string result = msg;
	string::size_type i, j, k;
	i = 0;
	while ((j = result.find("%[", i)) != string::npos)
	{
		if ((result.size() < j + 2) || ((k = result.find(']', j + 2)) == string::npos))
		{
			break;
		}
		string name = result.substr(j + 2, k - j - 2);
		StringMapIterC smi = params.find(name);
		if (smi != params.end())
		{
			result.replace(j, k - j + 1, smi->second);
			i = j + smi->second.size();
		}
		else
		{
			i = k + 1;
		}
	}
	return result;
}

uint64_t Util::getDirSize(const string &sFullPath)
{
	uint64_t total = 0;
	
	WIN32_FIND_DATA fData;
	HANDLE hFind;
	
	hFind = FindFirstFile(Text::toT(sFullPath + "\\*").c_str(), &fData);
	
	if (hFind != INVALID_HANDLE_VALUE)
	{
		do
		{
			string name = Text::fromT(fData.cFileName);
			if (name == "." || name == "..")
				continue;
			if ((fData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) && !BOOLSETTING(SHARE_HIDDEN))
				continue;
			if (fData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			{
				string newName = sFullPath + PATH_SEPARATOR + name;
				if (stricmp(newName + PATH_SEPARATOR, SETTING(TEMP_DOWNLOAD_DIRECTORY)) != 0)
				{
					total += getDirSize(newName);
				}
			}
			else
			{
				// Not a directory, assume it's a file...make sure we're not sharing the settings file...
				if ((stricmp(name.c_str(), "DCPlusPlus.xml") != 0) &&
				        (stricmp(name.c_str(), "Favorites.xml") != 0))
				{
				
					total += (uint64_t)fData.nFileSizeLow | ((uint64_t)fData.nFileSizeHigh) << 32;
				}
			}
		}
		while (FindNextFile(hFind, &fData));
	}
	
	FindClose(hFind);
	
	return total;
}

bool Util::validatePath(const string &sPath)
{
	if (sPath.empty())
		return false;
		
	if ((sPath.substr(1, 2) == ":\\") || (sPath.substr(0, 2) == "\\\\"))
	{
		if (GetFileAttributes(Text::toT(sPath).c_str()) & FILE_ATTRIBUTE_DIRECTORY)
			return true;
	}
	
	return false;
}

bool Util::fileExists(const string &aFile)
{
	DWORD attr = GetFileAttributes(Text::toT(aFile).c_str());
	return (attr != INVALID_FILE_ATTRIBUTES);// [!] IRainman fixs magical number
}

string Util::formatTime(const string &msg, const time_t t)
{
	if (!msg.empty())
	{
		tm* loc = localtime(&t);
		
		if (!loc)
		{
			return Util::emptyString;
		}
		
		size_t bufsize = msg.size() + 256;
		string buf(bufsize, 0);
		
		errno = 0;
		const size_t l_len = strftime(&buf[0], bufsize - 1, Text::fromUtf8(msg).c_str(), loc);
		buf.resize(l_len);
		
		while (buf.empty())
		{
			if (errno == EINVAL)
				return Util::emptyString;
				
			bufsize += 64;
			buf.resize(bufsize);
			buf.resize(strftime(&buf[0], bufsize - 1, Text::fromUtf8(msg).c_str(), loc));
		}
		
#ifdef _WIN32
		if (!Text::validateUtf8(buf))
#endif
		{
			buf = Text::toUtf8(buf);
		}
		return buf;
	}
	return Util::emptyString;
}

/* Below is a high-speed random number generator with much
   better granularity than the CRT one in msvc...(no, I didn't
   write it...see copyright) */
/* Copyright (C) 1997 Makoto Matsumoto and Takuji Nishimura.
   Any feedback is very welcome. For any question, comments,
   see http://www.math.keio.ac.jp/matumoto/emt.html or email
   matumoto@math.keio.ac.jp */
/* Period parameters */

// TODO oa?aou iaae?aneea ?enea!!!
#define N 624
#define M 397
#define MATRIX_A 0x9908b0df   /* constant vector a */
#define UPPER_MASK 0x80000000 /* most significant w-r bits */
#define LOWER_MASK 0x7fffffff /* least significant r bits */

/* Tempering parameters */
#define TEMPERING_MASK_B 0x9d2c5680
#define TEMPERING_MASK_C 0xefc60000
#define TEMPERING_SHIFT_U(y)  (y >> 11)
#define TEMPERING_SHIFT_S(y)  (y << 7)
#define TEMPERING_SHIFT_T(y)  (y << 15)
#define TEMPERING_SHIFT_L(y)  (y >> 18)

static unsigned long mt[N]; /* the array for the state vector  */
static int mti = N + 1; /* mti==N+1 means mt[N] is not initialized */

/* initializing the array with a NONZERO seed */
static void sgenrand(unsigned long seed)
{
	/* setting initial seeds to mt[N] using         */
	/* the generator Line 25 of Table 1 in          */
	/* [KNUTH 1981, The Art of Computer Programming */
	/*    Vol. 2 (2nd Ed.), pp102]                  */
	mt[0] = seed & ULONG_MAX;
	for (mti = 1; mti < N; mti++)
		mt[mti] = (69069 * mt[mti - 1]) & ULONG_MAX;
}

uint32_t Util::rand()
{
	unsigned long y;
	static unsigned long mag01[2] = {0x0, MATRIX_A};
	/* mag01[x] = x * MATRIX_A  for x=0,1 */
	
	if (mti >= N)   /* generate N words at one time */
	{
		int kk;
		
		if (mti == N + 1) /* if sgenrand() has not been called, */
			sgenrand(4357); /* a default initial seed is used   */
			
		for (kk = 0; kk < N - M; kk++)
		{
			y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK);
			mt[kk] = mt[kk + M] ^(y >> 1) ^ mag01[y & 0x1];
		}
		for (; kk < N - 1; kk++)
		{
			y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK);
			mt[kk] = mt[kk + (M - N)] ^(y >> 1) ^ mag01[y & 0x1];
		}
		y = (mt[N - 1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
		mt[N - 1] = mt[M - 1] ^(y >> 1) ^ mag01[y & 0x1];
		
		mti = 0;
	}
	
	y = mt[mti++];
	y ^= TEMPERING_SHIFT_U(y);
	y ^= TEMPERING_SHIFT_S(y) & TEMPERING_MASK_B;
	y ^= TEMPERING_SHIFT_T(y) & TEMPERING_MASK_C;
	y ^= TEMPERING_SHIFT_L(y);
	
	return y;
}

/*  getIpCountry
	This function returns the full country name of an ip, eg "Portugal".
    more info: http://www.maxmind.com/app/csv
*/
const string& Util::getIpCountry(const string& IP)
{
	if (!IP.empty()) // !SMT!-IP - avoid assertion failure
	{
		Lock l(g_cs);
		typedef unordered_map<string, string> TCacheMap;
		static TCacheMap g_cache_locations;
		TCacheMap::const_iterator l_locations = g_cache_locations.find(IP);
		if (l_locations != g_cache_locations.end())
			return l_locations->second;
		dcassert(count(IP.begin(), IP.end(), '.') == 3);
		if(count(IP.begin(), IP.end(), '.') != 3)
			return emptyString;
		unsigned u1, u2, u3, u4;
		const int iItems = sscanf_s(IP.c_str(), "%u.%u.%u.%u", &u1, &u2, &u3, &u4);
		uint32_t ipnum = 0;
		if (iItems == 4)
		{
			ipnum = (u1 << 24) + (u2 << 16) + (u3 << 8) + u4;
			// !SMT!-IP
			for (LocationsList::const_iterator j = userLocations.begin(); j != userLocations.end(); ++j)
				if (j->startip <= ipnum && ipnum < j->endip)
				{
					g_cache_locations[IP] = j->m_description;
					return j->m_description;
				}
#ifdef PPA_INCLUDE_COUNTRYLIST
			auto i = countries.lower_bound(ipnum);
			if(i != countries.end())
			{
				return countryNames[i->second];
			}
#endif
		}
	}
	return emptyString;
}

string Util::getTimeString()
{
	char buf[64];
	time_t _tt;
	time(&_tt);
	tm* _tm = localtime(&_tt);
	if (_tm == NULL)
	{
		strcpy(buf, "xx:xx:xx");
	}
	else
	{
		strftime(buf, 64, "%X", _tm);
	}
	return buf;
}

string Util::toAdcFile(const string& file)
{
	if (file == "files.xml.bz2" || file == "files.xml")
		return file;
		
	string ret;
	ret.reserve(file.length() + 1);
	ret += '/';
	ret += file;
	for (string::size_type i = 0; i < ret.length(); ++i)
	{
		if (ret[i] == '\\')
		{
			ret[i] = '/';
		}
	}
	return ret;
}
string Util::toNmdcFile(const string& file)
{
	if (file.empty())
		return Util::emptyString;
		
	string ret(file.substr(1));
	for (string::size_type i = 0; i < ret.length(); ++i)
	{
		if (ret[i] == '/')
		{
			ret[i] = '\\';
		}
	}
	return ret;
}

string Util::translateError(int aError)
{
#ifdef _WIN32
	LPTSTR lpMsgBuf;
	DWORD chars = FormatMessage(
	                  FORMAT_MESSAGE_ALLOCATE_BUFFER |
	                  FORMAT_MESSAGE_FROM_SYSTEM |
	                  FORMAT_MESSAGE_IGNORE_INSERTS,
	                  NULL,
	                  aError,
	                  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
	                  (LPTSTR) & lpMsgBuf,
	                  0,
	                  NULL
	              );
	if (chars == 0)
	{
		return string();
	}
	string tmp = Text::fromT(lpMsgBuf);
	// Free the buffer.
	LocalFree(lpMsgBuf);
	string::size_type i = 0;
	
	while ((i = tmp.find_first_of("\r\n", i)) != string::npos)
	{
		tmp.erase(i, 1);
	}
	return tmp;
#else // _WIN32
	return Text::toUtf8(strerror(aError));
#endif // _WIN32
}

TCHAR* Util::strstr(const TCHAR *str1, const TCHAR *str2, int *pnIdxFound)
{
	TCHAR *s1, *s2;
	TCHAR *cp = const_cast<TCHAR*>(str1);
	if (!*str2)
		return const_cast<TCHAR*>(str1);
	int nIdx = 0;
	while (*cp)
	{
		s1 = cp;
		s2 = (TCHAR *) str2;
		while (*s1 && *s2 && !(*s1 - *s2))
			s1++, s2++;
		if (!*s2)
		{
			if (pnIdxFound != NULL)
				*pnIdxFound = nIdx;
			return cp;
		}
		cp++;
		nIdx++;
	}
	if (pnIdxFound != NULL)
		*pnIdxFound = -1;
	return NULL;
}

string Util::formatStatus(int iStatus)
{
	string status;
	
	if (iStatus & Identity::NORMAL)
	{
		status += "Normal ";
	}
	if (iStatus & Identity::AWAY)
	{
		status += "Away ";
	}
	if (iStatus & Identity::SERVER)
	{
		status += "Fileserver ";
	}
	if (iStatus & Identity::FIREBALL)
	{
		status += "Fireball ";
	}
	if (iStatus & Identity::TLS)
	{
		status += "TLS ";
	}
	
	return (status.empty() ? "Unknown " : status) + "(" + toString(iStatus) + ")";
}

/* natural sorting */

int Util::DefaultSort(const wchar_t *a, const wchar_t *b, bool noCase /*=  true*/)
{
	/*
	if(BOOLSETTING(NAT_SORT))
	    //[-]PPA TODO
	{
	
	    int v1, v2;
	    while (*a != 0 && *b != 0)
	    {
	        v1 = 0;
	        v2 = 0;
	        bool t1 = isNumeric(*a);
	        bool t2 = isNumeric(*b);
	        if (t1 != t2) return (t1) ? -1 : 1;
	
	        if (!t1 && noCase)
	        {
	            if (Text::toLower(*a) != Text::toLower(*b))
	                return ((int)Text::toLower(*a)) - ((int)Text::toLower(*b));
	            a++;
	            b++;
	        }
	        else if (!t1)
	        {
	            if (*a != *b)
	                return ((int)*a) - ((int)*b);
	            a++;
	            b++;
	        }
	        else
	        {
	            while (isNumeric(*a))
	            {
	                v1 *= 10;
	                v1 += *a - '0';
	                a++;
	            }
	
	            while (isNumeric(*b))
	            {
	                v2 *= 10;
	                v2 += *b - '0';
	                b++;
	            }
	
	            if (v1 != v2)
	                return (v1 < v2) ? -1 : 1;
	        }
	    }
	
	    return noCase ? (((int)Text::toLower(*a)) - ((int)Text::toLower(*b))) : (((int)*a) - ((int)*b));
	
	
	    // [+] brain-ripper
	    // TODO:
	    // implement dynamic call to StrCmpLogicalW (this function not exist on Win2000.
	    // Note that this function is case insensitive
	    // return StrCmpLogicalW(a, b);
	
	}
	else*/
	{
		return noCase ? lstrcmpi(a, b) : lstrcmp(a, b);
	}
}

string Util::formatMessage(const string& message)
{
	string tmp = message;
	// Check all '<' and '[' after newlines as they're probably pasts...
	size_t i = 0;
	while ((i = tmp.find('\n', i)) != string::npos)
	{
		if (i + 1 < tmp.length())
		{
			if (tmp[i + 1] == '[' || tmp[i + 1] == '<')
			{
				tmp.insert(i + 1, "- ");
				i += 2;
			}
		}
		i++;
	}
	return Text::toDOS(tmp);
}

void Util::setLimiter(bool aLimiter)
{
	SET_SETTING(THROTTLE_ENABLE, aLimiter);
}

#ifdef PPA_INCLUDE_OLD_INNOSETUP_WIZARD
//[+]PPA
string Util::getRegistryValueString(const string& p_key, bool p_is_path)
{
	HKEY hk;
	TCHAR l_buf[512];
	l_buf[0] = 0;
	if (::RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\FlylinkDC++"), 0, KEY_READ, &hk) == ERROR_SUCCESS)
	{
		DWORD l_bufLen = sizeof(l_buf);
		::RegQueryValueEx(hk, Text::toT(p_key).c_str(), NULL, NULL, (LPBYTE)l_buf, &l_bufLen);
		::RegCloseKey(hk);
		if (l_bufLen)
		{
			string l_result = Text::fromT(l_buf);
			if (p_is_path)
				AppendPathSeparator(l_result); //[+]PPA
			return l_result;
		}
	}
	return emptyString;
}
#endif
//[+] SSA
size_t
Util::getDataFromInet(LPCWSTR agent, const DWORD frameBufferSize, string const& url, string& data, LONG timeOut /*=0*/, IDateReceiveReporter* reporter /* = NULL */)
{
	size_t totalBytesRead = 0;
	DWORD numberOfBytesRead = 0;
	DWORD numberOfBytesToRead = frameBufferSize;
	
	AutoArray<char> pTempData(frameBufferSize + 1);
	try
	{
	
		HINTERNET hInternet = InternetOpen(agent, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
		if (timeOut)
		{
			InternetSetOption(hInternet, INTERNET_OPTION_CONNECT_TIMEOUT, &timeOut, sizeof(timeOut));
		}
		HINTERNET hURL = InternetOpenUrlA(hInternet, url.c_str(), NULL, 0, INTERNET_FLAG_TRANSFER_ASCII | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_RELOAD, 1);
		
		bool isUserCancel = false;
		while (InternetReadFile(hURL, pTempData.get(), numberOfBytesToRead, &numberOfBytesRead))
		{
			if (numberOfBytesRead == 0)
			{
				// EOF detected
				break;
			}
			else
			{
				// Copy partial data
				pTempData[numberOfBytesRead] = '\0';
				data += pTempData.get();
				totalBytesRead += numberOfBytesRead;
				if (reporter){
					if ( !reporter->ReportResultReceive(numberOfBytesRead) )
					{
						isUserCancel = true;
						break;
					}
				}
			}
		}
		// Close URL
		InternetCloseHandle(hURL);
		
		// Close Internet session
		InternetCloseHandle(hInternet);

		if (isUserCancel)
		{
			data = emptyString;
			totalBytesRead = 0;
		}
	}
	catch (...)
	{
		data = emptyString;
		totalBytesRead = 0;
	}
	return totalBytesRead;
}


//[+] SSA
int64_t
Util::getDataFromInet(LPCWSTR agent, const DWORD frameBufferSize, string const& url, boost::shared_array<byte>& dataOut, LONG timeOut /*=0*/, IDateReceiveReporter* reporter /* = NULL */)
{
	uint64_t totalBytesRead = 0;
	DWORD numberOfBytesRead = 0;
	DWORD numberOfBytesToRead = frameBufferSize;
	vector<byte*> vecOut(numberOfBytesToRead);
	
	AutoArray<uint8_t> pTempData(frameBufferSize + 1);
	try
	{
	
		HINTERNET hInternet = InternetOpen(agent, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
		if (timeOut)
		{
			InternetSetOption(hInternet, INTERNET_OPTION_CONNECT_TIMEOUT, &timeOut, sizeof(timeOut));
		}
		HINTERNET hURL = InternetOpenUrlA(hInternet, url.c_str(), NULL, 0, INTERNET_FLAG_TRANSFER_ASCII | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_RELOAD, 1);
		
		bool isUserCancel = false;
		while (InternetReadFile(hURL, pTempData.get(), numberOfBytesToRead, &numberOfBytesRead))
		{
			if (numberOfBytesRead == 0)
			{
				// EOF detected
				break;
			}
			else
			{
				// Copy partial data
				// pTempData[numberOfBytesRead] = '\0';
				// data += pTempData.get();
				if (totalBytesRead + numberOfBytesRead > vecOut.size())
				{
					vecOut.resize(totalBytesRead + numberOfBytesRead);
				}
				memcpy(&vecOut[totalBytesRead], pTempData.get(), numberOfBytesRead);
				totalBytesRead += numberOfBytesRead;
				if (reporter){
					if ( !reporter->ReportResultReceive(numberOfBytesRead) ) {
						isUserCancel = true;
						break;
					}
				}
			}
		}
		// Close URL
		InternetCloseHandle(hURL);
		
		// Close Internet session
		InternetCloseHandle(hInternet);
		
		if (isUserCancel){
			dataOut.reset();
			totalBytesRead = 0;
		}else{
			dataOut = boost::shared_array<byte>(new byte[ totalBytesRead ]);
			for (size_t i = 0; i < totalBytesRead; i += frameBufferSize)
			{
				size_t lenCopy = frameBufferSize;
				if (totalBytesRead - i < frameBufferSize)
					lenCopy = totalBytesRead - i;

				byte* ptrCopy = dataOut.get() + i;
				memcpy(ptrCopy, &vecOut[i], lenCopy);
			}
		}
	}
	catch (...)
	{
		dataOut.reset();
		totalBytesRead = 0;
	}
	return totalBytesRead;
}

// [-] IRainman
//bool Util::IsXPSP3AndHigher()
//{
//	OSVERSIONINFOEX ver;
//	if (!getVersionInfo(ver))
//		return false;
//		
//	if (ver.dwMajorVersion >= 6)
//		return true;
//	if (ver.dwMajorVersion == 5 && ver.dwMinorVersion > 1)
//		return true;
//	if (ver.dwMajorVersion == 5 && ver.dwMinorVersion == 1 && ver.wServicePackMajor >= 3)
//		return true;
//		
//	return false;
//}
// [-] IRainman
//bool Util::getVersionInfo(OSVERSIONINFOEX& ver)
//{
//	// version can't change during process lifetime
//	if (osvi.dwOSVersionInfoSize != 0)
//	{
//		ver = osvi;
//		return true;
//	}
//	
//	memzero(&ver, sizeof(OSVERSIONINFOEX));
//	ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
//	
//	if (!GetVersionEx((OSVERSIONINFO*)&ver))
//	{
//		ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
//		if (!GetVersionEx((OSVERSIONINFO*)&ver))
//		{
//			return false;
//		}
//	}
//	
//	if (&ver != &osvi) osvi = ver;
//	return true;
//}

bool
Util::getTTH_MD5(const string& filename, const int buffSize, boost::shared_ptr<TigerTree>* tth, boost::shared_ptr<MD5Calc>* l_md5 /* = 0 */, bool isAbsPath/* = true*/)
{
	if (tth == NULL)
		return false;
	AutoArray<unsigned char> buf(buffSize);
	
	try
	{
		File f(filename, File::READ, File::OPEN, isAbsPath);
		
		*tth = boost::shared_ptr<TigerTree>(new TigerTree(TigerTree::calcBlockSize(f.getSize(), 1)));
		if (l_md5)
		{
			*l_md5 = boost::shared_ptr<MD5Calc>(new MD5Calc());
			(*l_md5).get()->MD5Init();
		}
		
		if (f.getSize() > 0)
		{
			size_t n = buffSize;
			while ((n = f.read(buf.get(), n)) > 0)
			{
				(*tth).get()->update(buf.get(), n);
				if (l_md5)
					(*l_md5).get()->MD5Update(buf.get(), n);
				n = buffSize;
			}
		}
		else
		{
			(*tth).get()->update("", 0);
		}
		f.close();
		(*tth).get()->finalize();
		return true;
	}
	catch (FileException)
	{
		// No File
	}
	
	return false;
}


/**
 * @file
 * $Id: Util.cpp 570 2011-07-27 17:07:22Z bigmuscle $
 */